/*
 * Copyright 2010 Thedwick, LLC

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */
package com.thedwick.jdbcGrabber.analysis.parser;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FileBasedResultsParser implements ResultsParser {
		private final File sqlCountsFile;
		private final File sqlTableFile;
		private final File traceCountsFile;
		private final File traceTableFile;
		private final File traceMappingFile;
		protected Map<Integer, String> sqlTable = new HashMap<Integer, String>();
		protected Map<Integer, String> traceTable = new HashMap<Integer, String>();
		protected Map<Integer, Integer> traceCounts = new HashMap<Integer, Integer>();
		protected Map<Integer, Integer> sqlCounts = new HashMap<Integer, Integer>();
		protected Map<Integer, Set<Integer>> traceMapping = new HashMap<Integer, Set<Integer>>();
		
		public FileBasedResultsParser(String sinkName, String basePath){
			String sqlTableFileName = makePath(sinkName, basePath,"sqlTable");
			String traceTableFileName = makePath(sinkName, basePath,"traceTable");
			String sqlCountsFileName = makePath(sinkName, basePath,"sqlCounts");
			String traceCountsFileName = makePath(sinkName, basePath,"traceCounts");
			String traceMappingFileName = makePath(sinkName, basePath,"traceMapping");
			this.sqlTableFile = new File(sqlTableFileName);
			this.sqlCountsFile = new File(sqlCountsFileName);
			this.traceTableFile = new File(traceTableFileName);
			this.traceCountsFile = new File(traceCountsFileName);
			this.traceMappingFile = new File(traceMappingFileName);
		}
		
		private String makePath(String sinkName, String basePath, String prefix) {
			return basePath + File.separator + prefix + "." + sinkName + ".dat";
		}

		public Set<CapturedStatement> parse() throws IOException{
			Set<CapturedStatement> rv = new HashSet<CapturedStatement>();
			readAllFiles();
			
			for (Integer sqlKey : sqlTable.keySet()){
				String sql = sqlTable.get(sqlKey);
				Set<Integer> traceIds = traceMapping.get(sqlKey);
				Set<CapturedTrace> traces = parseTraces(traceIds);
				CapturedStatement stmt = parseSql(sql, traces, this.sqlCounts.get(sqlKey));
				rv.add(stmt);
			}
			return rv;
		}

		private CapturedStatement parseSql(String sql, Set<CapturedTrace> traces, int count) throws IOException {
			CapturedStatement rv = new ZqlCapturedStatement(sql, count, traces);
			return rv;
		}

		private Set<CapturedTrace> parseTraces(Set<Integer> traceIds) {
			Set<CapturedTrace> rv = new HashSet<CapturedTrace>();
			for (Integer traceId : traceIds){
				rv.add(parseTrace(traceId));
			}
			return rv;
		}
		
		
		/* expects a stack trace to look something like this:
		 * music.cpp.server.MonitoredStatement.executeQuery(MonitoredStatement.java:83)
		 * 
		 * PS: this is handy for testing regular expressions: 
		 * http://www.cis.upenn.edu/~matuszek/General/RegexTester/regex-tester.html
		 */
		private CapturedTrace parseTrace(Integer traceId) {
			CapturedTrace rv =  new CapturedTrace(this.traceCounts.get(traceId));
			String traceString = this.traceTable.get(traceId);
			Pattern pattern = Pattern.compile("([a-zA-Z0-9_\\.]*)\\.([a-zA-Z0-9_\\.]*)\\([a-zA-Z0-9_\\.]*:([\\d]*)\\)");
			Matcher matcher = pattern.matcher(traceString);
			while (matcher.find()){
				String className = matcher.group(1);
				if (!className.startsWith("com.thedwick.jdbcGrabber")){//leave out noise from this library itself.
					String methodName = matcher.group(2);
					int lineNumber = Integer.parseInt(matcher.group(3) == null ? "0" : matcher.group(3));
					CapturedTraceElement element = new CapturedTraceElement(className, methodName, lineNumber);
					rv.add(element);
				}
			}
			return rv;
		}

		protected void readAllFiles() throws IOException {
			readSqlTable();
			readTraceTable();
			readTraceCounts();
			readSqlCounts();
			readTraceMapping();
		}

		/**
		 * expects the format:
		 * 133090089 : -722608805,519470703
		 * 
		 * one line at a time where the left hand side is a SQL id and the 
		 * right hand side is one or more trace ids separated by commas
		 * @throws IOException 
		 */
		private void readTraceMapping() throws IOException {
			readFile(this.traceMappingFile, new LineProcessor(){
				@Override
				public void process(String line){
					String[] elements = line.split(":");
					Integer sqlId = safeint(elements[0]);
					String[] traces = elements[1].split(",");
					Set<Integer> traceList = new HashSet<Integer>(traces.length);
					for (String trace : traces){
						traceList.add(safeint(trace));
					}
					FileBasedResultsParser.this.traceMapping.put(sqlId, traceList);
				}
			});
			
		}

		/**
		 * Expects the format:
		 * 133090089 : 1
		 * 
		 * one line at a time
		 * @throws IOException 
		 */
		private void readSqlCounts() throws IOException {
			readFile(this.sqlCountsFile, new LineProcessor(){
				@Override
				public void process(String line){
					String[] elements = line.split(":");
					FileBasedResultsParser.this.sqlCounts.put(safeint(elements[0]), safeint(elements[1]));
				}
			});
			
		}

		/**
		 * expects the format:
		 * -722608805 : 1
		 * 
		 * one line at a time
		 * @throws IOException 
		 */
		private void readTraceCounts() throws IOException {
			readFile(this.traceCountsFile, new LineProcessor(){
				@Override
				public void process(String line){
					String[] elements = line.split(":");
					FileBasedResultsParser.this.traceCounts.put(safeint(elements[0]), safeint(elements[1]));
				}
			});			
		}

		/**
		 * expects the format:
		 * -722608805 : 
com.thedwick.jdbcGrabber.util.ThreadUtil.getStackTrace(ThreadUtil.java:11)
com.thedwick.jdbcGrabber.sink.LimitedQueueSink.consume(LimitedQueueSink.java:74)
com.thedwick.jdbcGrabber.statement.gbPreparedStatement.executeQuery(gbPreparedStatement.java:59)
music.cpp.server.MonitoredStatement.executeQuery(MonitoredStatement.java:83)
music.cpp.maintenance.job.FromMusicBrainzUpdateSongToAlbumMappingInMusicDBJob.cacheMusicIPArtistMbids(FromMusicBrainzUpdateSongToAlbumMappingInMusicDBJob.java:394)
music.cpp.maintenance.job.FromMusicBrainzUpdateSongToAlbumMappingInMusicDBJob.reallyDoWork(FromMusicBrainzUpdateSongToAlbumMappingInMusicDBJob.java:74)
music.cpp.maintenance.job.FromMusicBrainzUpdateSongToAlbumMappingInMusicDBJob.doWork(FromMusicBrainzUpdateSongToAlbumMappingInMusicDBJob.java:50)
music.cpp.maintenance.MaintenanceJobRunner.main(MaintenanceJobRunner.java:31)
|
			with a pipe character on its own line separating each trace
		 * @throws IOException 
		 */
		private void readTraceTable() throws IOException {
			readFile(this.traceTableFile, new LineProcessor(){
				private StringBuffer aTrace = new StringBuffer();
				private Integer id = null;
				
				public void process(String line){
					if (line.startsWith("|")){//assume we just finished a stack trace
						FileBasedResultsParser.this.traceTable.put(this.id, aTrace.toString());
						this.id = null;
						aTrace = new StringBuffer();
					} else if (this.id == null) { //assume we're on the first line of a new trace
						String[] elements = line.split(":");
						this.id = safeint(elements[0]);
					} else { //assume this is just another line of the same trace
						aTrace.append(line).append("\n");
					}
				}
			});
			
		}

		/** 
		 * expects the format:
		 * 1621354335 : SELECT mbid, artist_id, name FROM specific_artists
		 * 
		 * one line at a time
		 * @throws IOException 
		 */
		private void readSqlTable() throws IOException {
			readFile(this.sqlTableFile, new LineProcessor(){
				public void process(String line){
					String[] elements = line.split(":");
					FileBasedResultsParser.this.sqlTable.put(safeint(elements[0]), elements[1]);
				}

			});
		}


		/**
		 * If only Java had closures!
		 *
		 */
		private abstract class LineProcessor {
			protected Integer safeint(String string) {
				return new Integer(string.trim());
			}

			public abstract void process(String line);
		}

		private void readFile(File file, LineProcessor lineProcessor) throws IOException {
			BufferedReader reader = null;
			try {
				reader = new BufferedReader(new FileReader(file));
				String line = reader.readLine();
				while(line != null){
					lineProcessor.process(line);
					line = reader.readLine();
				}
			} finally {
				try {
					reader.close();
				} catch (Exception e){
					e.printStackTrace();
				}
			}
		}

}
